# -*- coding: utf-8 -*-
"""
Created on Fri Jul 26 17:07:28 2024

@author: xiaobojie
"""

# 生成正弦曲线的动画

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

# 创建数据
x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(x)

fig, ax = plt.subplots()
line, = ax.plot(x, y)

# 更新函数
def update(num, x, y, line):
    line.set_data(x[:num], y[:num])  # 更新数据
    return line,

ani = animation.FuncAnimation(fig, update, frames=len(x), fargs=[x, y, line], interval=100)
plt.show()

#%%

# 生成2个点移动的动画


import matplotlib.pyplot as plt
import matplotlib.animation as animation

# 创建图形和轴
fig = plt.figure()
ax = plt.axes(xlim=(0, 10), ylim=(0, 10))

# 初始化空白点列表
pts, = ax.plot([], [], markersize=10, c='k', marker='o', ls='None')
beak, = ax.plot([], [], markersize=4, c='r', marker='o', ls='None')

# 初始化函数
def init():
    pts.set_data([], [])
    beak.set_data([], [])
    return pts, beak

# 更新函数
def update(frame):
    x = [frame]  # 使用列表包装 frame
    y = [frame]  # 使用列表包装 frame
    pts.set_data(x, y)
    beak.set_data([x[0] + 0.5], [y[0] + 0.5])  # 为 beak 也提供列表数据
    return pts, beak

# 创建动画
ani = animation.FuncAnimation(fig, update, frames=range(10), init_func=init, blit=True)

plt.show()

#%%

import numpy as np
from scipy.spatial.distance import squareform, pdist, cdist






#%%aa

import numpy as np

# 定义参数
width = 100.0
height = 100.0
n = 10  # 生成10个点

# 计算位置
pos = [width/2.0, height/2.0] + 10 * np.random.rand(2 * n).reshape(n, 2)

# 输出生成的位置
print("Generated positions:")
print(pos)
#%%
import sys, argparse
import numpy as np
import numpy.linalg as norm

import matplotlib.pyplot as plt
import matplotlib.animation as anim

from scipy.spatial.distance import squareform, pdist, cdist

width, height = 640, 480

class Boids:
    def __init__(self, n):
        
        # 创建鸟群个体的位置，pos是n个元素的2维list
        
        # np.random.rand(2 * n) 生成一个长度为 2 * n 的一维数组，数组中的每个数字都是在 [0, 1) 区间内的随机浮点数。
        # .reshape(n, 2) 将这个一维数组转换成一个 n×2n×2 的二维数组。在这个二维数组中，每一行都可以看作是一个点的 x,yx,y 坐标。
        # +执行了broadcast
        self.pos = [width/2.0, height/2.0] + 10 * np.random.rand(2 * n).reshape(n, 2)
        
        # 生成随机角度
        # angles只能在init里面用，self.pos可以在其他方法中用？
        angles = 2 * np.pi * np.random.rand(n)
        
        # list(zip([0,1,2],[3,4,5]))
        # >>> [(0, 3), (1, 4), (2, 5)]
        
        # 速度是合并角度的正弦和余弦值
        self.vel = np.array(list(zip(np.sin(angles), np.cos(angles))))
        self.n = n
        
        #？
        self.min_dist = 25.0
        self.max_rule_vel = .03
        self.max_vel = 2.0
        
        
        
    def tick(self, frame_num, pts, beak):
        #?
        self.dist_matrix = squareform(pdist(self.pos))
        
        self.vel += self.apply_rules()
        self.limit(self.vel, self.max_vel)
        self.pos += self.vel
        self.apply_bc()
        
        
        
        # set_data 是 Matplotlib 中的一个方法，用于设置 Line2D 对象（通常是通过 plot 
        # 方法创建的线条）的数据。具体来说，它用来更新线条的 x 和 y 数据。
        # 在使用 Matplotlib 创建动画或动态更新图形时，set_data 方法通常被用来更新图形对象的坐标，
        # 从而实现动画效果。
        
        pts.set_data(self.pos.reshape(2*self.n)[::2], 
                     self.pos.reshape(2*self.n)[1::2])
        
        # 计算方向矢量？self.vel/self.max_vel是什么
        vec = self.pos + 10*self.vel/self.max_vel


        # 头的位置
        # vec.reshape(2*self.n) 将 vec 变为一个形状为 (2*self.n,) 的一维数组。
        # vec.reshape(2*self.n)[::2] 提取偶数索引的元素，作为 x 数据。
        # vec.reshape(2*self.n)[1::2] 提取奇数索引的元素，作为 y 数据。
        beak.set_data(vec.reshape(2*self.n)[::2], 
                      vec.reshape(2*self.n)[1::2])
        
        
    def limit_vec(self, vec, max_val):
        
        # ?
        mag = norm(vec)
        if mag > max_val:
            vec[0], vec[1] = vec[0]*max_val/mag, vec[1]*max_val/mag
            
    
    
    def limit(self, x, max_val):
        
        for vec in x:
            self.limit_vec(vec, max_val)
        
        
    def apply_bc(self):
        '''
        平铺小块边界条件，超出右边界，则从左边界进入；超出左边界，则从右边界进入
        超出上边界，则从下边界进入；超出下边界，则从上边界进入
        '''
        # 移动边界缓冲区
        delta_r = 2.0
        
        for coord in self.pos:
            if coord[0] > width + delta_r:
                coord[0] = -delta_r
            if coord[0] < -delta_r:
                coord[0] = width + delta_r
            if coord[1] > height + delta_r:
                coord[1] = -delta_r
            if coord[1] < -delta_r:
                coord[1] = height + delta_r
                
                
    
    def apply_rules(self):
        
        # self.dist_matrix中小于25的项？
        d = self.dist_matrix < 25.0
        
        vel = self.pos*d.sum(axis=1).reshape(self.n, 1) - d.dot(self.pos)
        self.limit(vel, self.max_rule_vel)
        
        
        d = self.dist_matrix < 50.0
        
        vel_2 = d.dot(self.vel)
        self.limit(vel_2, self.max_rule_vel)
        vel += vel_2
        
        vel_3 = d.dot(self.pos) - self.pos
        self.limit(vel_3, self.max_rule_vel)
        vel += vel_3
        
        return vel
    
    
    
    def butt_press(self, event):
        
        # event是什么对象？
        # 可否用event.button == 1？
        if event.button is 1:
            self.pos = np.concatenate((self.pos, np.array([[event.xdata, event.ydata]])), axis=0)
            angles = 2*np.pi*np.random.rand(1)
            v = np.array(list(zip(np.sin(angles), np.cos(angles))))
            self.vel = np.concatenate((self.vel, v), axis=0)
            self.n += 1
        
        elif event.button is 3:
            self.vel += 0.1*(self.pos - np.array([[event.xdata, event.ydata]]))
            

def tick(frame_num, pts, beak, boids):
    
    boids.tick(frame_num, pts, beak)
    return pts, beak



def main():
    
    print('开始鸟群模拟……')
    parser = argparse.ArgumentParser(description='进行Renoid鸟群模拟……')
    
    parser.add_argument('--num_boids', dest='n', required=False)
    args = parser.parse_args()

    n = 100
    if args.n:
        n = int(args.n)
        
    boids = Boids(n)
    
    # ？
    fig = plt.figure()
    ax = plt.axes(xlim=(0, width), ylim=(0, height))
    
    
    
    # 空白列表：[] 表示空列表，意味着这些点列表在初始化时没有任何数据。这种用法通常用
    # 于动画或动态更新的图形中，后续会逐帧或根据需要添加数据点。
    
    # 绘制鸟的身体
    pts, = ax.plot([], [], markersize=10, c='k', marker='o', ls='None')
    # 绘制鸟的头
    beak, = ax.plot([], [], markersize=4, c='r', marker='o', ls='None')
    
    
    # 这句有用？
    anim_a = anim.FuncAnimation(fig, tick, fargs=(pts, beak, boids), interval=50)
    
    # ？
    cid = fig.canvas.mpl_connect('button_press_event', boids.butt_press)
    
    plt.show()
    
    
if __name__ == '__main__':
    main()
    